#include "slice2cube.h"
#include <tiffio.h>
#include <cstring>
#include "public/utils.h"

class Tiff {
	private:
		TIFF* ptr;
	public:
		Tiff(const char* p, const char* m) {
			ptr=TIFFOpen(p, m);
		}
		~Tiff() {
			if(ptr) {
				TIFFClose(ptr);
				ptr=nullptr;
			}
		}
		operator TIFF*() { return ptr; }
		bool operator!() { return !ptr; }
};

template<bool TILED>
inline uint64_t getTileSize(TIFF* tif);
template<>
inline uint64_t getTileSize<true>(TIFF* tif) {
	return TIFFTileSize(tif);
}
template<>
inline uint64_t getTileSize<false>(TIFF* tif) {
	return TIFFStripSize(tif);
}

template<uint16 PC>
inline uint64_t getTileBufSize(uint64_t tileSize, uint16 spp);
template<>
inline uint64_t getTileBufSize<PLANARCONFIG_CONTIG>(uint64_t tileSize, uint16 spp) {
	return tileSize;
}
template<>
inline uint64_t getTileBufSize<PLANARCONFIG_SEPARATE>(uint64_t tileSize, uint16 spp) {
	return tileSize*spp;
}

template<bool TILED>
inline uint32 getYCoord(uint32 j, uint32 h);
template<>
inline uint32 getYCoord<true>(uint32 j, uint32 h) {
	return h-1-j;
}
template<>
inline uint32 getYCoord<false>(uint32 j, uint32 h) {
	return j;
}

template<bool TILED, uint16 PC>
inline void readTile(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y);
template<>
inline void readTile<true, PLANARCONFIG_CONTIG>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	auto tile=TIFFComputeTile(tif, x, y, 0, 0);
	if(-1==TIFFReadEncodedTile(tif, tile, bufTile, tileSize))
		throwError("Failed to read tile.\n");
}
template<>
inline void readTile<false, PLANARCONFIG_CONTIG>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	auto strip=TIFFComputeStrip(tif, y, 0);
	if(-1==TIFFReadEncodedStrip(tif, strip, bufTile, tileSize)) {
		std::memset(bufTile, 0, tileSize);
		printMessage("Failed to read strip.\n");
	}
}
template<>
inline void readTile<true, PLANARCONFIG_SEPARATE>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	for(tsample_t s=0; s<spp; s++) {
		auto tile=TIFFComputeTile(tif, x, y, 0, s);
		if(-1==TIFFReadEncodedTile(tif, tile, bufTile+s*tileSize, tileSize))
			throwError("Failed to read tile.\n");
	}
}
template<>
inline void readTile<false, PLANARCONFIG_SEPARATE>(TIFF* tif, uint8_t* bufTile, int64_t tileSize, uint16 spp, uint32 x, uint32 y) {
	for(tsample_t s=0; s<spp; s++) {
		auto strip=TIFFComputeStrip(tif, y, s);
		if(-1==TIFFReadEncodedStrip(tif, strip, bufTile+s*tileSize, tileSize)) {
			std::memset(bufTile+s*tileSize, 0, tileSize);
			printMessage("Failed to read strip.\n");
		}
	}
}

template<uint16 PC>
inline uint8_t* getBufStart(uint8_t* bufTile, uint64_t tileSize, tsample_t s);
template<>
inline uint8_t* getBufStart<PLANARCONFIG_CONTIG>(uint8_t* bufTile, uint64_t tileSize, tsample_t s) {
	return bufTile;
}
template<>
inline uint8_t* getBufStart<PLANARCONFIG_SEPARATE>(uint8_t* bufTile, uint64_t tileSize, tsample_t s) {
	return bufTile+s*tileSize;
}

template<uint16 PC>
inline uint32 getBufIndex(uint32 idx, uint16 spp, tsample_t s);
template<>
inline uint32 getBufIndex<PLANARCONFIG_CONTIG>(uint32 idx, uint16 spp, tsample_t s) {
	return idx*spp+s;
}
template<>
inline uint32 getBufIndex<PLANARCONFIG_SEPARATE>(uint32 idx, uint16 spp, tsample_t s) {
	return idx;
}

const int conv1bit[]={0, 255};
const int conv2bit[]={0, 85, 170, 255};
const int conv4bit[]={0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};

template<uint16 BPS, typename T>
inline T getValuePal(uint8_t* bufStart, uint32 bufIdx);
template<>
inline uint8_t getValuePal<1, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/8]>>(7-bufIdx%8))&0x01;
}
template<>
inline uint8_t getValuePal<2, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/4]>>(3-bufIdx%4)*2)&0x03;
}
template<>
inline uint8_t getValuePal<4, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return (bufStart[bufIdx/2]>>(1-bufIdx%2)*4)&0x0F;
}
template<>
inline uint8_t getValuePal<8, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return bufStart[bufIdx];
}
template<>
inline uint16_t getValuePal<16, uint16_t>(uint8_t* bufStart, uint32 bufIdx) {
	uint16* p=reinterpret_cast<uint16*>(bufStart+bufIdx*2);
	return *p;
}

template<uint16 BPS, typename T>
inline T getValue(uint8_t* bufStart, uint32 bufIdx);
template<>
inline uint8_t getValue<1, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv1bit[(bufStart[bufIdx/8]>>(7-bufIdx%8))&0x01];
}
template<>
inline uint8_t getValue<2, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv2bit[(bufStart[bufIdx/4]>>(3-bufIdx%4)*2)&0x03];
}
template<>
inline uint8_t getValue<4, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return conv4bit[(bufStart[bufIdx/2]>>(1-bufIdx%2)*4)&0x0F];
}
template<>
inline uint8_t getValue<8, uint8_t>(uint8_t* bufStart, uint32 bufIdx) {
	return bufStart[bufIdx];
}
template<>
inline uint16_t getValue<16, uint16_t>(uint8_t* bufStart, uint32 bufIdx) {
	uint16_t* p=reinterpret_cast<uint16_t*>(bufStart+bufIdx*2);
	return *p;
}

template<typename T, typename T0, uint16 BPS, uint16 PC, bool TILED>
inline void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* bufSlice, const uint8_t* xfunc, int jobidx, int jobn, uint32 tileW, uint32 tileH, uint16 spp) {
	uint16* colors[3];
	if(!TIFFGetField(tif, TIFFTAG_COLORMAP, &colors[0], &colors[1], &colors[2]))
		throwError("Failed to read colormap.\n");
	auto tileSize=getTileSize<TILED>(tif);
	auto tileBufSize=getTileBufSize<PC>(tileSize, spp);
	if(bufTile.size()<tileBufSize)
		bufTile.resize(tileBufSize);
	uint64_t tilenx=((width+tileW-1)/tileW);
	uint64_t tilen=((height+tileH-1)/tileH)*tilenx;
	uint64_t tile_start=jobidx*tilen/jobn;
	uint64_t tile_end=(jobidx+1)*tilen/jobn;
	for(uint64_t tilei=tile_start; tilei<tile_end; tilei++) {
		auto y=tilei/tilenx*tileH;
		auto x=tilei%tilenx*tileW;
		readTile<TILED, PC>(tif, bufTile.data(), tileSize, spp, x, y);
		uint32 w=tileW;
		if(w+x>width) {
			w=width-x;
		}
		uint32 h=tileH;
		if(h+y>height) {
			h=height-y;
		}
		for(tsample_t s=0; s<spp; s++) {
			uint8_t *bufStart=getBufStart<PC>(bufTile.data(), tileSize, s);
			auto bufOut=bufSlice+x+y*width+s*3*width*height;
			for(uint32 j=0; j<h; j++) {
				for(uint32 i=0; i<w; i++) {
					auto bufIdx=getBufIndex<PC>(j*tileW+i, spp, s);
					auto vp=getValuePal<BPS, T0>(bufStart, bufIdx);
					for(tsample_t c=0; c<3; c++) {
						auto v=colors[c][vp];
						T ov;
						if(sizeof(ov)==sizeof(v)) {
							ov=v;
						} else {
							ov=xfunc[v];
						}
						bufOut[i+getYCoord<TILED>(j, h)*width+c*width*height]=ov;
					}
				}
			}
		}
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC, bool TILED>
inline void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* bufSlice, const uint8_t* xfunc, int jobidx, int jobn, uint32 tileW, uint32 tileH, uint16 spp) {
	auto tileSize=getTileSize<TILED>(tif);
	auto tileBufSize=getTileBufSize<PC>(tileSize, spp);
	if(bufTile.size()<tileBufSize)
		bufTile.resize(tileBufSize);

	uint64_t tilenx=((width+tileW-1)/tileW);
	uint64_t tilen=((height+tileH-1)/tileH)*tilenx;
	uint64_t tile_start=jobidx*tilen/jobn;
	uint64_t tile_end=(jobidx+1)*tilen/jobn;
	for(uint64_t tilei=tile_start; tilei<tile_end; tilei++) {
		auto y=tilei/tilenx*tileH;
		auto x=tilei%tilenx*tileW;
		readTile<TILED, PC>(tif, bufTile.data(), tileSize, spp, x, y);
		uint32 w=tileW;
		if(w+x>width) {
			w=width-x;
		}
		uint32 h=tileH;
		if(h+y>height) {
			h=height-y;
		}
		for(tsample_t s=0; s<spp; s++) {
			uint8_t *bufStart=getBufStart<PC>(bufTile.data(), tileSize, s);
			auto bufOut=bufSlice+x+y*width+s*width*height;
			for(uint32 j=0; j<h; j++) {
				for(uint32 i=0; i<w; i++) {
					auto bufIdx=getBufIndex<PC>(j*tileW+i, spp, s);
					auto v=getValue<BPS, T0>(bufStart, bufIdx);
					T ov;
					if(sizeof(T0)!=sizeof(T)) {
						ov=xfunc[v];
					} else {
						ov=v;
					}
					bufOut[i+getYCoord<TILED>(j, h)*width]=ov;
				}
			}
		}
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	if(tiled) {
		return convertTile<T, T0, BPS, PC, true>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tileW, tileH, spp);
	} else {
		return convertTile<T, T0, BPS, PC, false>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tileW, tileH, spp);
	}
}
template<typename T, typename T0, uint16 BPS, uint16 PC>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	if(tiled) {
		return convertTilePal<T, T0, BPS, PC, true>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tileW, tileH, spp);
	} else {
		return convertTilePal<T, T0, BPS, PC, false>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tileW, tileH, spp);
	}
}
template<typename T, typename T0, uint16 BPS>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	switch(pc) {
		case PLANARCONFIG_CONTIG:
			return convertTile<T, T0, BPS, PLANARCONFIG_CONTIG>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tiled, tileW, tileH, spp);
		case PLANARCONFIG_SEPARATE:
			return convertTile<T, T0, BPS, PLANARCONFIG_SEPARATE>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tiled, tileW, tileH, spp);
		default:
			throwError("Planar configuration ", pc, " not supported.\n");
	}
}
template<typename T, typename T0, uint16 BPS>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	switch(pc) {
		case PLANARCONFIG_CONTIG:
			return convertTilePal<T, T0, BPS, PLANARCONFIG_CONTIG>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tiled, tileW, tileH, spp);
		case PLANARCONFIG_SEPARATE:
			return convertTilePal<T, T0, BPS, PLANARCONFIG_SEPARATE>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, tiled, tileW, tileH, spp);
		default:
			throwError("Planar configuration ", pc, " not supported.\n");
	}
}
template<typename T>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp);
template<>
void convertTile<uint8_t>(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, uint8_t* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	switch(bps) {
		case 1:
			return convertTile<uint8_t, uint8_t, 1>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 2:
			return convertTile<uint8_t, uint8_t, 2>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 4:
			return convertTile<uint8_t, uint8_t, 4>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 8:
			return convertTile<uint8_t, uint8_t, 8>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 16:
			return convertTile<uint8_t, uint16_t, 16>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<>
void convertTile<uint16_t>(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, uint16_t* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	switch(bps) {
		case 16:
			return convertTile<uint16_t, uint16_t, 16>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<typename T>
void convertTilePal(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn, uint16 bps, uint16 pc, bool tiled, uint32 tileW, uint32 tileH, uint16 spp) {
	switch(bps) {
		case 1:
			return convertTilePal<T, uint8_t, 1>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 2:
			return convertTilePal<T, uint8_t, 2>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 4:
			return convertTilePal<T, uint8_t, 4>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 8:
			return convertTilePal<T, uint8_t, 8>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		case 16:
			return convertTilePal<T, uint16_t, 16>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, pc, tiled, tileW, tileH, spp);
		default:
			throwError("Bits per sample ", bps, " not supported.\n");
	}
}
template<typename T>
void convertTile(TIFF* tif, uint64_t width, uint64_t height, std::vector<uint8_t>& bufTile, T* buf, const uint8_t* xfunc, int jobidx, int jobn) {
	uint16 pm;
	if(!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &pm))
		throwError("Failed to read PHOTOMETRIC.\n");
	uint16 bps;
	if(!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps))
		throwError("Failed to read BITSPERSAMPLE.\n");
	uint16 pc;
	if(!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &pc))
		throwError("Failed to read PLANARCONFIG.\n");
	bool tiled=TIFFIsTiled(tif);

	uint32 tileW, tileH;
	if(tiled) {
		if(!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileW)
				|| !TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileH))
			throwError("Failed to get tile width/length information.\n");
	} else { // A strip is like a tile.
		tileW=width;
		if(!TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &tileH))
			throwError("Failed to get rows/strip information.");
	}
	uint16 spp;
	if(!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp))
		throwError("Failed to read SAMPLESPERPIXEL.\n");

	switch(pm) {
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_MINISBLACK:
		case PHOTOMETRIC_SEPARATED:
		case PHOTOMETRIC_RGB:
			return convertTile<T>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, bps, pc, tiled, tileW, tileH, spp);
		case PHOTOMETRIC_PALETTE:
			return convertTilePal<T>(tif, width, height, bufTile, buf, xfunc, jobidx, jobn, bps, pc, tiled, tileW, tileH, spp);
		default:
			// Not supporting colormap
			throwError("Photometric type ", pm, " not supported.\n");
	}
}

template <typename T>
void Slice2Cube<T>::readInputFiles(Slices* slices, int jobi) {
	for(size_t si=0; si<slices->bufs.size(); si++) {
		auto zidx=slices->zidx+si;
		if(zidx>=inputpairs.size())
			throwError("Internal error. No file names.");
		auto& fn=*inputpairs[zidx].first;
		if(fn=="BLACK") {
			if(jobi==0) {
				memset(slices->bufs[si], 0, width*height*channels*sizeof(T));
			}
			continue;
		}
		if(fn=="WHITE") {
			if(jobi==0) {
				memset(slices->bufs[si], 0xFF, width*height*channels*sizeof(T));
			}
			continue;
		}
		std::vector<uint8_t> bufTile;
		Tiff tif(fn.c_str(), "r");
		if(!tif)
			throwError("Error opening TIFF file: '", fn, "'. Abort.\n");
		if(!TIFFSetDirectory(tif, inputpairs[zidx].second))
			throwError("Cannot set directory");
		convertTile<T>(tif, width, height, bufTile, slices->bufs[si], xfunc, jobi, slices->total);
	}
}
template void Slice2Cube<uint8_t>::readInputFiles(Slices* slices, int jobi);
template void Slice2Cube<uint16_t>::readInputFiles(Slices* slices, int jobi);

void checkSlice(TIFF* tif, uint32* pwidth, uint32* pheight, uint16* pchannels, uint16* pbpc) {
	uint16 spp, pm, pc, bps;
	if(!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, pwidth)
			|| !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, pheight))
		throwError("No width/length information in file: ");
	if(*pwidth==0 || *pheight==0)
		throwError("Zero width/length: ");
	if(!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp))
		throwError("Failed to read SAMPLESPERPIXEL.");
	if(spp==0)
		throwError("Zero sample per pixel");
	if(!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &pm))
		throwError("Failed to read PHOTOMETRIC.");
	switch(pm) {
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_MINISBLACK:
		case PHOTOMETRIC_SEPARATED:
			break;
		case PHOTOMETRIC_RGB:
			if(spp!=3)
				throwError("Wrong spp for RGB image");
			break;
		case PHOTOMETRIC_PALETTE:
			if(spp!=1)
				throwError("Wrong spp for PALETTE image");
			break;
		default:
			throwError("Unsupported photometric");
	}
	if(!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &pc))
		throwError("Failed to read PLANARCONFIG.");
	switch(pc) {
		case PLANARCONFIG_CONTIG:
		case PLANARCONFIG_SEPARATE:
			break;
		default:
			throwError("Unsupported planar config");
	}
	if(!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps))
		throwError("Failed to read BITSPERSAMPLE.");
	switch(bps) {
		case 1:
		case 2:
		case 4:
		case 8:
		case 16:
			break;
		default:
			throwError("Unsupported bits per sample");
	}
	*pchannels=(pm==PHOTOMETRIC_PALETTE)?3*spp:spp;
	*pbpc=(pm==PHOTOMETRIC_PALETTE)?16:bps;
}

void checkImages(const std::vector<std::string>& inputfiles, uint64_t* pwidth, uint64_t* pheight, uint64_t* pdepth, int* pchannels, int* pbpc, std::vector<std::pair<const std::string*, int>>* inputpairs, bool skip_check, uint64_t depth_hint) {
	TIFFSetWarningHandler(nullptr);
	uint64_t width=0;
	uint64_t height=0;
	uint64_t depth=0;
	int channels=0;
	int bpc=0;
	inputpairs->clear();
	if(skip_check) {
		Tiff tif{inputfiles[0].c_str(), "r"};
		if(!tif)
			throwError("Error opening TIFF file: '", inputfiles[0], "'.");
		uint32 slice_width, slice_height;
		uint16 slice_channels, slice_bpc;
		checkSlice(tif, &slice_width, &slice_height, &slice_channels, &slice_bpc);
		if(depth_hint) {
			depth=depth_hint;
		} else {
			depth=inputfiles.size();
		}
		width=slice_width;
		height=slice_height;
		channels=slice_channels;
		bpc=slice_bpc;
	} else {
		bool first_slice=true;
		depth=0;
		for(size_t i=0; i<inputfiles.size(); i++) {
			if(inputfiles[i]=="BLACK" || inputfiles[i]=="WHITE") {
				inputpairs->push_back({&inputfiles[i], 0});
				depth++;
				continue;
			}
			Tiff tif(inputfiles[i].c_str(), "r");
			if(!tif)
				throwError("Error opening TIFF file: '", inputfiles[i], "'. Abort.");
			do {
				uint32 slice_width, slice_height;
				uint16 slice_channels, slice_bpc;
				inputpairs->push_back({&inputfiles[i], TIFFCurrentDirectory(tif)});
				checkSlice(tif, &slice_width, &slice_height, &slice_channels, &slice_bpc);
				if(first_slice) {
					width=slice_width;
					height=slice_height;
					channels=slice_channels;
					bpc=slice_bpc;
					first_slice=false;
				} else {
					if(width!=slice_width || height!=slice_height)
						//throwError("Inconsistant width/height among slices.");
						printMessage("Inconsistant width/height among slices:", inputfiles[i]);
					if(channels!=slice_channels)
						throwError("Inconsistant number of channels among slices.");
					if(bpc!=slice_bpc)
						throwError("Inconsistant number of bits per channel among slices.");
				}
				depth++;
			} while(TIFFReadDirectory(tif));
		}
	}
	*pwidth=width;
	*pheight=height;
	*pdepth=depth;
	*pchannels=channels;
	*pbpc=bpc;
}

template <typename T>
void Slice2Cube<T>::closeTiffFiles() {
	if(tiffd) {
		TIFFClose(tiffd);
		tiffd=nullptr;
	}
}
template void Slice2Cube<uint8_t>::closeTiffFiles();
template void Slice2Cube<uint16_t>::closeTiffFiles();

template <typename T>
void Slice2Cube<T>::getNextImage() {
	if(!tiffd) {
		if(tiffidx>=inputfiles.size())
			return;
		if(inputfiles[tiffidx]=="BLACK" || inputfiles[tiffidx]=="WHITE") {
			inputpairs.push_back({&inputfiles[tiffidx++], 0});
			return;
		}
		tiffd=TIFFOpen(inputfiles[tiffidx].c_str(), "r");
		if(!tiffd)
			throwError("Failed to open.");
		inputpairs.push_back({&inputfiles[tiffidx], TIFFCurrentDirectory(tiffd)});
		return;
	} else {
		if(TIFFReadDirectory(tiffd)) {
			inputpairs.push_back({&inputfiles[tiffidx], TIFFCurrentDirectory(tiffd)});
			return;
		}
		TIFFClose(tiffd);
		tiffd=nullptr;
		tiffidx++;
		return getNextImage();
	}
}
template void Slice2Cube<uint8_t>::getNextImage();
template void Slice2Cube<uint16_t>::getNextImage();

